
package com.jf.cloud.supplier.service.impl;

import com.jf.cloud.api.auth.feign.AccountFeignClient;
import com.jf.cloud.api.auth.vo.AuthAccountVO;
import com.jf.cloud.api.biz.feign.NotifyFeignClient;
import com.jf.cloud.api.leaf.feign.SegmentFeignClient;
import com.jf.cloud.api.platform.feign.ConfigFeignClient;
import com.jf.cloud.api.platform.vo.SysConfigApiVO;
import com.jf.cloud.api.supplier.vo.SupplierApiDetailVO;
import com.jf.cloud.common.cache.constant.LockCacheNames;
import com.jf.cloud.common.cache.util.RedisUtil;
import com.jf.cloud.common.constant.Constant;
import com.jf.cloud.common.constant.DistributedIdKey;
import com.jf.cloud.common.constant.SendTypeEnum;
import com.jf.cloud.common.database.dto.PageDTO;
import com.jf.cloud.common.database.util.PageUtil;
import com.jf.cloud.common.database.vo.PageVO;
import com.jf.cloud.common.exception.LuckException;
import com.jf.cloud.common.response.ResponseEnum;
import com.jf.cloud.common.response.ServerResponseEntity;
import com.jf.cloud.common.rocketmq.config.RocketMqConstant;
import com.jf.cloud.common.security.AuthUserContext;
import com.jf.cloud.common.util.DateUtils;
import com.jf.cloud.common.util.Json;
import com.jf.cloud.supplier.bo.mongo.MongoSupplierWalletLogBO;
import com.jf.cloud.supplier.constant.*;
import com.jf.cloud.supplier.dto.SupplierWalletDTO;
import com.jf.cloud.supplier.dto.SupplierWithdrawCashConfigDto;
import com.jf.cloud.supplier.dto.SupplierWithdrawCashDTO;
import com.jf.cloud.supplier.mapper.SupplierWalletMapper;
import com.jf.cloud.supplier.mapper.SupplierWalletZoneMapper;
import com.jf.cloud.supplier.mapper.SupplierWithdrawCashMapper;
import com.jf.cloud.supplier.model.SupplierWithdrawCash;
import com.jf.cloud.supplier.service.*;
import com.jf.cloud.supplier.vo.*;
import ma.glasnost.orika.MapperFacade;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * 供应商提现申请信息
 *
 * @author zz
 * @date 2022-03-14 17:04:32
 */
@Service
public class SupplierWithdrawCashServiceImpl implements SupplierWithdrawCashService {

    @Autowired
    private SupplierWithdrawCashMapper supplierWithdrawCashMapper;
    @Autowired
    private SupplierDetailService supplierDetailService;
    @Autowired
    private NotifyFeignClient sendNotifyFeignClient;
    @Autowired
    private AccountFeignClient accountFeignClient;
    @Autowired
    private SupplierWalletService supplierWalletService;
    @Autowired
    private SupplierBankCardService supplierBankCardService;
    @Autowired
    private SupplierWalletMapper supplierWalletMapper;
    @Autowired
    private MapperFacade mapperFacade;
    @Autowired
    private SegmentFeignClient segmentFeignClient;
    @Autowired
    private RocketMQTemplate addSupplierWalletLogTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private SupplierWalletZoneService supplierWalletZoneService;
    @Autowired
    private SupplierWalletZoneMapper supplierWalletZoneMapper;

    @Autowired
    private ConfigFeignClient configFeignClient;

    @Override
    public PageVO<SupplierWithdrawCash> page(PageDTO pageDTO) {
        return PageUtil.doPage(pageDTO, () -> supplierWithdrawCashMapper.list());
    }

    @Override
    public SupplierWithdrawCash getByCashId(Long cashId) {
        return supplierWithdrawCashMapper.getByCashId(cashId);
    }

    @Override
    public void save(SupplierWithdrawCash supplierWithdrawCash) {
        supplierWithdrawCashMapper.save(supplierWithdrawCash);
    }

    @Override
    public void update(SupplierWithdrawCash supplierWithdrawCash) {
        supplierWithdrawCashMapper.update(supplierWithdrawCash);
    }

    @Override
    public void deleteById(Long cashId) {
        supplierWithdrawCashMapper.deleteById(cashId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void apply(SupplierWithdrawCashDTO supplierWithdrawCashDTO, Long supplierId) {
        Date now = new Date();
        this.checkWithdrawInfo(supplierWithdrawCashDTO, supplierId);
        SupplierWithdrawCash supplierWithdrawCash = mapperFacade.map(supplierWithdrawCashDTO, SupplierWithdrawCash.class);
        supplierWithdrawCash.setUpdateTime(now);
        supplierWithdrawCash.setCreateTime(now);
        supplierWithdrawCash.setStatus(SupplierWithdrawCashStatus.WAITAUDIT.value());
        supplierWithdrawCash.setSupplierId(supplierId);
        this.checkConfig(supplierWithdrawCash);
        SupplierWalletVO supplierWallet = supplierWalletService.getBySupplierId(supplierId);
        if (supplierWithdrawCash.getAmount() > supplierWallet.getSettledAmount()) {
            // 可提现金额不足
            throw new LuckException("可提现金额不足");
        }
        // 插入提现申请记录
        supplierWithdrawCashMapper.save(supplierWithdrawCash);

        Long changeAmount = supplierWithdrawCashDTO.getAmount();

        // 更新钱包的结算金额和冻结金额
        updateSupplierWalletByApply(supplierWithdrawCash);

        List<MongoSupplierWalletLogBO> supplierWalletLogList = new ArrayList<>();
        // 保存提现记录
        this.saveSupplierWalletLog(changeAmount, supplierId, now, SupplierWalletChangeReason.APPLY_CASH, supplierWalletLogList);

        // 用mq批量保存钱包日志
        SendStatus sendWalletLogStatus = addSupplierWalletLogTemplate.syncSend(RocketMqConstant.ADD_SUPPLIER_WALLET_LOG_TOPIC, new GenericMessage<>(supplierWalletLogList)).getSendStatus();
        if (!Objects.equals(sendWalletLogStatus, SendStatus.SEND_OK)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
    }

    @Override
    public PageVO<SupplierWithdrawCashVO> pageByParam(PageDTO pageDTO, SupplierWithdrawCashDTO supplierWithdrawCashDTO) {
        return PageUtil.doPage(pageDTO, () -> supplierWithdrawCashMapper.listByParam(supplierWithdrawCashDTO));
    }
    @Override
    public List<SupplierWithdrawCashLogExcelVO> listSupplierWithdrawCashLogExcel(SupplierWithdrawCashDTO supplierWithdrawCashDTO) {
        return supplierWithdrawCashMapper.listSupplierWithdrawCashLogExcel(supplierWithdrawCashDTO);
    }
    @Override
    public SupplierWithdrawCashVO getDetailByCashId(Long cashId) {
        return supplierWithdrawCashMapper.getDetailByCashId(cashId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void auditWithdrawCash(SupplierWithdrawCashDTO supplierWithdrawCashDTO, Long userId) {
        SupplierWithdrawCashVO supplierWithdrawCashVO = supplierWithdrawCashMapper.getDetailByCashId(supplierWithdrawCashDTO.getCashId());
        if (Objects.isNull(supplierWithdrawCashVO)) {
            throw new LuckException("未找到申请信息");
        }
        if (!Objects.equals(supplierWithdrawCashVO.getStatus(), SupplierWithdrawCashStatus.WAITAUDIT.value())) {
            throw new LuckException("该申请已被审核");
        }
        this.checkAuditInfo(supplierWithdrawCashDTO, supplierWithdrawCashDTO.getSupplierId());
        SupplierWithdrawCash supplierWithdrawCash = mapperFacade.map(supplierWithdrawCashVO, SupplierWithdrawCash.class);
        Date now = new Date();
        supplierWithdrawCash.setAuditingTime(now);
        supplierWithdrawCash.setAuditorId(userId);
        supplierWithdrawCash.setStatus(supplierWithdrawCashDTO.getStatus());
        supplierWithdrawCash.setRemarks(supplierWithdrawCashDTO.getRemarks());
        supplierWithdrawCash.setPayingAccount(supplierWithdrawCashDTO.getPayingAccount());
        supplierWithdrawCash.setPayingTime(supplierWithdrawCashDTO.getPayingTime());
        supplierWithdrawCash.setPayingCardNo(supplierWithdrawCashDTO.getPayingCardNo());
        Long supplierId = supplierWithdrawCash.getSupplierId();
        Long changeAmount = supplierWithdrawCash.getAmount();
        List<MongoSupplierWalletLogBO> supplierWalletLogList = new ArrayList<>();

        SupplierWalletChangeReason supplierWalletChangeReason = SupplierWalletChangeReason.PASS_CASH;
        if (!Objects.equals(changeAmount, 0L)) {
            switch (Objects.requireNonNull(SupplierWithdrawCashStatus.instance(supplierWithdrawCash.getStatus()))) {
                case SUCCESSAUDIT:
                    // 审核通过, 减去冻结的金额
                    boolean subFreezeAmountSuccess = supplierWalletMapper.subFreezeAmount(supplierId, changeAmount) > 0;
                    if (!subFreezeAmountSuccess) {
                        throw new LuckException("冻结金额不足，提现失败");
                    }
                    break;
                case FAILAUDIT:
                    // 审核不通过，将冻结的金额返回到已结算金额
                    boolean subFreezeAmount = supplierWalletMapper.subFreezeAmount(supplierId, changeAmount) > 0;
                    if (!subFreezeAmount) {
                        throw new LuckException("审核失败，冻结金额异常");
                    }

                    RLock lock = redissonClient.getLock(LockCacheNames.LOCK_SUPPLIER_WALLET_ZONE_PREFIX + supplierId);
                    try {
                        lock.lock();
                        // 增加结算金额到主分片
                        boolean addSettledAmount = supplierWalletMapper.addSettledAmount(supplierId, changeAmount) > 0;
                        if (!addSettledAmount) {
                            throw new LuckException("审核失败，结算金额异常");
                        }
                    } finally {
                        lock.unlock();
                    }
                    supplierWalletChangeReason = SupplierWalletChangeReason.REFUSE_CASH;
                    break;
                default:
                    // 审核状态有误
                    throw new LuckException("审核状态有误");
            }
        }

        // 保存日志
        this.saveSupplierWalletLog(changeAmount, supplierId, now, supplierWalletChangeReason, supplierWalletLogList);
        // 用mq批量保存钱包日志
        SendStatus sendWalletLogStatus = addSupplierWalletLogTemplate.syncSend(RocketMqConstant.ADD_SUPPLIER_WALLET_LOG_TOPIC, new GenericMessage<>(supplierWalletLogList)).getSendStatus();
        if (!Objects.equals(sendWalletLogStatus, SendStatus.SEND_OK)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }

        if (supplierWithdrawCashMapper.updateByIdAndStatus(supplierWithdrawCash, supplierWithdrawCash.getCashId(), SupplierWithdrawCashStatus.WAITAUDIT.value()) != 1) {
            throw new LuckException("该申请已被审核");
        }
    }

    @Override
    public void saveConfig(SupplierWithdrawCashConfigDto supplierWithdrawCashConfigDto) {
        checkParam(supplierWithdrawCashConfigDto);
        SysConfigApiVO sysConfigApiVO = new SysConfigApiVO();
        sysConfigApiVO.setParamKey(Constant.SUPPLIE_WITHDRWA);
        sysConfigApiVO.setRemark("供应商提现配置");
        sysConfigApiVO.setParamValue(Json.toJsonString(supplierWithdrawCashConfigDto));
        configFeignClient.saveOrUpdateSysConfig(sysConfigApiVO);
    }

    @Override
    public SupplierWithdrawCashConfigDto getConfig() {
        SupplierWithdrawCashConfigDto supplierWithdrawCashConfigDto = Json.parseObject(configFeignClient.getConfig(Constant.SUPPLIE_WITHDRWA).getData(),SupplierWithdrawCashConfigDto.class);
        if(Objects.isNull(supplierWithdrawCashConfigDto)){
            supplierWithdrawCashConfigDto = new SupplierWithdrawCashConfigDto();
            supplierWithdrawCashConfigDto.setWithdrawCashLeast(100D);
            supplierWithdrawCashConfigDto.setFrequency(1);
            supplierWithdrawCashConfigDto.setNumber(1);
        }
        return supplierWithdrawCashConfigDto;
    }

    private void checkParam(SupplierWithdrawCashConfigDto supplierWithdrawCashConfigDto) {
        Double least = supplierWithdrawCashConfigDto.getWithdrawCashLeast();
        Double max = supplierWithdrawCashConfigDto.getWithdrawCashMax();
        Integer frequency = supplierWithdrawCashConfigDto.getFrequency();
        Integer number = supplierWithdrawCashConfigDto.getNumber();
        if(Objects.isNull(least)||least<1){
            throw new LuckException("最少金额设置错误");
        }
        if(Objects.nonNull(max)&&max<least){
            throw new LuckException("最大金额设置错误");
        }
        if(Objects.isNull(frequency)||(!frequency.equals(WithdrawCashType.WEEKEND.value())&&!frequency.equals(WithdrawCashType.MONTH.value())&&!frequency.equals(WithdrawCashType.YEAR.value()))){
            throw new LuckException("提现频率设置错误");
        }
        if(Objects.isNull(number)||number<1){
            throw new LuckException("提现次数设置错误");
        }
    }

    private void checkConfig(SupplierWithdrawCash shopWithdrawCash) {
        SupplierWithdrawCashConfigDto config = getConfig();
        if(shopWithdrawCash.getAmount()<config.getWithdrawCashLeast()){
            throw new LuckException("提现金额过少，最低提现:"+config.getWithdrawCashLeast()/100);
        }
        if(Objects.nonNull(config.getWithdrawCashMax())&&shopWithdrawCash.getAmount()>config.getWithdrawCashMax()){
            throw new LuckException("提现金额过大,最高提现:"+config.getWithdrawCashMax()/100);
        }
        Integer frequency = config.getFrequency();
        Date startTimes;
        Date endTimes;
        String date;
        if(frequency.equals(WithdrawCashType.WEEKEND.value())){
            startTimes = DateUtils.getCurrentWeekStartTimes(new Date());
            endTimes = DateUtils.getCurrentWeekEndTimes(new Date());
            date = WithdrawCashType.WEEKEND.date();
        } else if (frequency.equals(WithdrawCashType.MONTH.value())) {
            startTimes = DateUtils.getMonthFirstOrLastDay(new Date(), 0);
            endTimes = DateUtils.getMonthFirstOrLastDay(new Date(), 1);
            date = WithdrawCashType.MONTH.date();
        } else {
            startTimes = DateUtils.getCurrentYearStartTime(new Date());
            endTimes = DateUtils.getCurrentYearEndTime(new Date());
            date = WithdrawCashType.YEAR.date();
        }
        int count = checkCount(startTimes, endTimes, shopWithdrawCash);
        if(count+1>config.getNumber()){
            throw new LuckException("超出提现频率限制,当前频率为:"+date+" "+config.getNumber()+"次");
        }
    }

    private int checkCount(Date startTimes, Date endTimes, SupplierWithdrawCash supplierWithdrawCash) {
        return supplierWithdrawCashMapper.getWithdrawCashCount(startTimes,endTimes,supplierWithdrawCash);
    }

    private void updateSupplierWalletByApply(SupplierWithdrawCash supplierWithdrawCash) {
        if (Objects.equals(supplierWithdrawCash.getAmount(), 0L)) {
            return;
        }
        Long supplierId = supplierWithdrawCash.getSupplierId();
        Long changeAmount = supplierWithdrawCash.getAmount();
        // 1. 增加冻结金额
        boolean addFreezeAmountSuccess = supplierWalletMapper.addFreezeAmount(supplierId, changeAmount) > 0;
        if (!addFreezeAmountSuccess) {
            throw new LuckException("提现申请发生异常");
        }
        // 2. 减少结算金额
        RLock lock = redissonClient.getLock(LockCacheNames.LOCK_SUPPLIER_WALLET_ZONE_PREFIX + supplierId);
        try {
            lock.lock();
            SupplierWalletZoneInfoVO supplierWalletZoneInfo = supplierWalletZoneService.getShopZoneInfo(supplierId);
            if (Objects.equals(supplierWalletZoneInfo.getZoneNum(), SupplierWalletZoneConstant.ZERO)) {
                // 如果没有分片就从原钱包扣
                boolean settledAmountSuccess = supplierWalletMapper.subSettledAmount(supplierId, changeAmount) > 0;
                if (!settledAmountSuccess) {
                    throw new LuckException("可提现金额不足");
                }
            } else {
                Random random = new Random();
                int zoneNo = random.nextInt(supplierWalletZoneInfo.getZoneNum());

                // 1.尝试从次分片扣
                boolean updateSuccess = supplierWalletZoneMapper.subSettledAmount(supplierId, changeAmount, zoneNo) > 0;
                if (updateSuccess) {
                    return;
                }

                // 2.尝试从主分片扣
                boolean mainUpdateSuccess = supplierWalletMapper.subSettledAmount(supplierId, changeAmount) > 0;
                if (mainUpdateSuccess) {
                    return;
                }

                // 3.聚合查询，不够扣就返回异常，足够扣就扣完后重新分片
                Long mainSettleAmount = supplierWalletMapper.getSettlementAmountBySupplierId(supplierId);

                Long sumLessZoneSettleAmount = supplierWalletZoneMapper.getSettleAmountBySupplierId(supplierId);

                long totalSettleAmount = mainSettleAmount + sumLessZoneSettleAmount;

                if (totalSettleAmount < changeAmount) {
                    throw new LuckException("可提现金额不足");
                }

                // 剩余的结算金额
                long remainSettleAmount = totalSettleAmount - changeAmount;
                // 把所有的结算金额再次均分
                supplierWalletZoneService.updateZoneSettleAmount(supplierId, remainSettleAmount, supplierWalletZoneInfo.getZoneNum());
            }
        } finally {
            lock.unlock();
        }
    }

    private void saveSupplierWalletLog(Long changeAmount, Long supplierId, Date now, SupplierWalletChangeReason supplierWalletChangeReason, List<MongoSupplierWalletLogBO> supplierWalletLogList) {

        // 插入结算金额修改记录【提现申请/提现申请被拒绝】
        if (Objects.equals(supplierWalletChangeReason, SupplierWalletChangeReason.APPLY_CASH) || Objects.equals(supplierWalletChangeReason, SupplierWalletChangeReason.REFUSE_CASH)) {
            MongoSupplierWalletLogBO supplierWalletLog = new MongoSupplierWalletLogBO();
            ServerResponseEntity<Long> response = segmentFeignClient.getSegmentId(DistributedIdKey.MALL4CLOUD_SHOP_WALLET_LOG);
            if (!response.isSuccess()) {
                throw new LuckException(ResponseEnum.EXCEPTION);
            }
            supplierWalletLog.setWalletLogId(response.getData());
            supplierWalletLog.setCreateTime(now);
            supplierWalletLog.setUpdateTime(now);
            supplierWalletLog.setSupplierId(supplierId);
            supplierWalletLog.setReason(supplierWalletChangeReason.value());
            supplierWalletLog.setChangeAmount(Math.abs(changeAmount));
            supplierWalletLog.setUserAmount(supplierWalletLog.getChangeAmount());
            supplierWalletLog.setIoType(Objects.equals(supplierWalletChangeReason, SupplierWalletChangeReason.APPLY_CASH) ? SupplierWalletIoType.EXPENDITURE.value() : SupplierWalletIoType.INCOME.value());
            supplierWalletLog.setAmountType(SupplierWalletAmountType.SETTLED_AMOUNT.value());
            supplierWalletLogList.add(supplierWalletLog);
        }

        // 插入冻结金额修改记录
        MongoSupplierWalletLogBO supplierWalletLog = new MongoSupplierWalletLogBO();
        supplierWalletLog.setCreateTime(now);
        supplierWalletLog.setUpdateTime(now);
        ServerResponseEntity<Long> response = segmentFeignClient.getSegmentId(DistributedIdKey.MALL4CLOUD_SHOP_WALLET_LOG);
        if (!response.isSuccess()) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        supplierWalletLog.setWalletLogId(response.getData());
        supplierWalletLog.setSupplierId(supplierId);
        supplierWalletLog.setReason(supplierWalletChangeReason.value());
        supplierWalletLog.setChangeAmount(Math.abs(changeAmount));
        supplierWalletLog.setUserAmount(supplierWalletLog.getChangeAmount());
        supplierWalletLog.setIoType(!Objects.equals(supplierWalletChangeReason, SupplierWalletChangeReason.APPLY_CASH) ? SupplierWalletIoType.EXPENDITURE.value() : SupplierWalletIoType.INCOME.value());
        supplierWalletLog.setAmountType(SupplierWalletAmountType.FREEZE_AMOUNT.value());
        supplierWalletLogList.add(supplierWalletLog);
    }

    private void checkWithdrawInfo(SupplierWithdrawCashDTO supplierWithdrawCashDTO, Long supplierId) {
        Integer sysType = AuthUserContext.get().getSysType();
        ServerResponseEntity<AuthAccountVO> merchantInfoRes = accountFeignClient.getAccountInfoByTenantId(supplierId, sysType);
        if (!merchantInfoRes.isSuccess() || Objects.isNull(merchantInfoRes.getData())) {
            throw new LuckException("商家信息获取出错");
        }
        ServerResponseEntity<Boolean> sendNotifyResponse = sendNotifyFeignClient.checkValidCode(merchantInfoRes.getData().getPhone(), supplierWithdrawCashDTO.getValidCode(), SendTypeEnum.VALID);
        if (!sendNotifyResponse.isSuccess() || !sendNotifyResponse.getData()) {
            throw new LuckException("验证码有误或已过期");
        }
        SupplierApiDetailVO supplierDetailVO = supplierDetailService.getBySupplierId(supplierId);
        if (Objects.isNull(supplierDetailVO)) {
            throw new LuckException("店铺不存在");
        }
        if (supplierWithdrawCashDTO.getAmount() < 1) {
            throw new LuckException("提现金额不能少于1.00元");
        }
        SupplierBankCardVO supplierBankCardVO = supplierBankCardService.getBySupplierBankCardId(supplierWithdrawCashDTO.getSupplierBankCardId());
        if (Objects.isNull(supplierBankCardVO) || !Objects.equals(supplierBankCardVO.getSupplierId(), supplierId)) {
            throw new LuckException("银行卡信息错误");
        }
    }

    private void checkAuditInfo(SupplierWithdrawCashDTO supplierWithdrawCashDTO, Long supplierId) {
        SupplierApiDetailVO supplierDetailVO = supplierDetailService.getBySupplierId(supplierId);
        if (Objects.isNull(supplierDetailVO)) {
            throw new LuckException("店铺不存在");
        }
        SupplierBankCardVO supplierBankCard = supplierBankCardService.getBySupplierBankCardId(supplierWithdrawCashDTO.getSupplierBankCardId());
        if (Objects.isNull(supplierBankCard) || !Objects.equals(supplierBankCard.getSupplierId(), supplierId)) {
            throw new LuckException("银行卡信息错误");
        }
    }

    @Override
    public void checkDrawCashAmount(Long amount) {
        // 基础数值检查
        if (amount < Constant.HUNDRED) {
            throw new LuckException("提现金额不能少于1.00元");
        }
        // 系统配置检查
        SupplierWithdrawCash supplierWithdrawCash = new SupplierWithdrawCash();
        supplierWithdrawCash.setAmount(amount);
        this.checkConfig(supplierWithdrawCash);
    }
}
